﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Roton.ZZT
{
    /// <summary>
    /// Execute Act code.
    /// </summary>
    /// <param name="mem">Memory to use.</param>
    /// <param name="index">Index of the Thing to execute.</param>
    delegate void ActDelegate(Context context, int index);

    /// <summary>
    /// Contains built-in element Act code.
    /// </summary>
    static class Act
    {
        static private int AbsDiff(int a, int b)
        {
            int diff = (a - b);
            if (diff < 0)
                return (b - a);
            else
                return diff;
        }

        static public void Bear(Context context, int index)
        {
            IMemory mem = context.Memory;
            IThing bear = mem.GetThing(index);
            IThing player = mem.GetThing(0);
            int vectorX;
            int vectorY;

            if ((bear.X == player.X) || ((8 - bear.P1) < AbsDiff(player.Y, bear.Y)))
            {
                if ((8 - bear.P1) < AbsDiff(bear.X, player.X))
                {
                    vectorX = 0;
                    vectorY = 0;
                }
                else
                {
                    vectorX = 0;
                    vectorY = context.PosNeg(bear.Y - player.Y);
                }
            }
            else
            {
                vectorX = context.PosNeg(player.X - bear.X);
                vectorY = 0;
            }

            int tileID = context.GetIDAt(bear.X + vectorX, bear.Y + vectorY);
            ElementType tileType = mem.GetElementType(tileID);

            if (mem.GetElement(tileID).Floor)
                context.MoveThing(index, bear.X + vectorX, bear.Y + vectorY);
            else if (tileType == ElementType.Player || tileType == ElementType.Breakable)
                context.Attack(index, bear.X + vectorX, bear.Y + vectorY);
        }

        static public void BlinkWall(Context context, int index)
        {
            IMemory mem = context.Memory;
            IThing blinkwall = mem.GetThing(index);

            if (blinkwall.P3 == 0)
                blinkwall.P3 = blinkwall.P1 + 1;

            if (blinkwall.P3 == 1)
            {
                int rayX;
                int rayY;
                ElementType rayElement;

                rayX = blinkwall.X + blinkwall.StepX;
                rayY = blinkwall.Y + blinkwall.StepY;
                rayElement = (blinkwall.StepX == 0) ? ElementType.BlinkRayV : ElementType.BlinkRayH;

                while ((context.GetElementTypeAt(rayX, rayY) == rayElement) && (context.GetColorAt(rayX, rayY) == context.GetColorAt(blinkwall.X, blinkwall.Y)))
                {
                    context.SetElementTypeAt(rayX, rayY, ElementType.Empty);
                    context.UpdateBoardAt(rayX, rayY);
                    rayX += blinkwall.StepX;
                    rayY += blinkwall.StepY;
                    blinkwall.P3 = (blinkwall.P2 * 2) + 1;
                }

                if ((rayX == blinkwall.X + blinkwall.StepX) && (rayY == blinkwall.Y + blinkwall.StepY))
                {
                    while (true)
                    {
                        bool rayBlocked = false;

                        if (context.GetElementTypeAt(rayX, rayY) != ElementType.Empty)
                        {
                            if (context.GetElementAt(rayX, rayY).Destructible)
                                context.DestroyAt(rayX, rayY);
                            if (context.GetElementTypeAt(rayX, rayY) == ElementType.Player)
                            {
                                int rayTargetIndex = context.GetThingIndexAt(rayX, rayY);
                                if (blinkwall.StepX == 0)
                                {
                                    // the condition that checks rayX - 1 transports
                                    // to rayX + 1; this is a bug in the original ZZT engine
                                    if (context.GetElementTypeAt(rayX - 1, rayY) == ElementType.Empty)
                                        context.MoveThing(rayTargetIndex, rayX + 1, rayY);
                                    else if (context.GetElementTypeAt(rayX - 1, rayY) == ElementType.Empty)
                                        context.MoveThing(rayTargetIndex, rayX + 1, rayY);
                                }
                                else
                                {
                                    if (context.GetElementTypeAt(rayX, rayY - 1) == ElementType.Empty)
                                        context.MoveThing(rayTargetIndex, rayX, rayY - 1);
                                    else if (context.GetElementTypeAt(rayX, rayY + 1) == ElementType.Empty)
                                        context.MoveThing(rayTargetIndex, rayX, rayY + 1);
                                }
                                if (context.GetElementTypeAt(rayX, rayY) == ElementType.Player)
                                {
                                    while (mem.World.Health > 0)
                                        context.HarmThing(rayTargetIndex);
                                    rayBlocked = true;
                                }
                            }
                        }

                        if (context.GetElementTypeAt(rayX, rayY) == ElementType.Empty)
                        {
                            context.SetElementTypeAt(rayX, rayY, rayElement);
                            context.SetColorAt(rayX, rayY, context.GetColorAt(blinkwall.X, blinkwall.Y));
                            context.UpdateBoardAt(rayX, rayY);
                        }
                        else
                            rayBlocked = true;

                        rayX += blinkwall.StepX;
                        rayY += blinkwall.StepY;
                        if (rayBlocked)
                            break;
                    }

                    blinkwall.P3 = (blinkwall.P2 * 2) + 1;
                }
            }
            else
            {
                blinkwall.P3--;
            }
        }

        static public void Bomb(Context context, int index)
        {
            IMemory mem = context.Memory;
            IThing bomb = mem.GetThing(index);

            if (bomb.P1 > 0)
            {
                bomb.P1--;
                context.UpdateBoardAt(bomb.X, bomb.Y);
                if (bomb.P1 == 1)
                {
                    context.PlaySound(1, SoundType.BombExplode);
                    context.UpdateRadius(bomb.X, bomb.Y, 1);
                }
                else if (bomb.P1 == 0)
                {
                    int bombX = bomb.X;
                    int bombY = bomb.Y;
                    context.RemoveThing(index);
                    context.UpdateRadius(bombX, bombY, 2);
                }
                else
                {
                    if ((bomb.P1 % 2) == 0)
                        context.PlaySound(1, SoundType.BombTock);
                    else
                        context.PlaySound(1, SoundType.BombTick);
                }
            }
        }

        static public void Bullet(Context context, int index)
        {
            IMemory mem = context.Memory;
            IThing bullet = mem.GetThing(index);

            int targetX;
            int targetY;
            int targetElement;
            bool canRicochet = true;

            while (true)
            {
                targetX = bullet.X + bullet.StepX;
                targetY = bullet.Y + bullet.StepY;
                targetElement = context.GetIDAt(targetX, targetY);

                if (mem.GetElement(targetElement).Floor || mem.GetElementType(targetElement) == ElementType.Water)
                    context.MoveThing(index, targetX, targetY);
                else if (mem.GetElementType(targetElement) == ElementType.Ricochet && canRicochet)
                {
                    bullet.StepX = -bullet.StepX;
                    bullet.StepY = -bullet.StepY;
                    context.PlaySound(1, SoundType.Ricochet);
                    canRicochet = false;
                    continue;
                }
                else
                {
                    if (mem.GetElementType(targetElement) != ElementType.Breakable && (!mem.GetElement(targetElement).Destructible || (mem.GetElementType(targetElement) != ElementType.Player && bullet.P1 != 0)))
                    {
                        if (context.GetElementTypeAt(bullet.X + bullet.StepY, bullet.Y + bullet.StepX) == ElementType.Ricochet && canRicochet)
                        {
                            targetX = bullet.StepX;
                            bullet.StepX = -bullet.StepY;
                            bullet.StepY = -targetX;
                            context.PlaySound(1, SoundType.Ricochet);
                            canRicochet = false;
                            continue;
                        }
                        else if (context.GetElementTypeAt(bullet.X + bullet.StepY, bullet.Y - bullet.StepX) == ElementType.Ricochet && canRicochet)
                        {
                            targetX = bullet.StepX;
                            bullet.StepX = bullet.StepY;
                            bullet.StepY = targetX;
                            context.PlaySound(1, SoundType.Ricochet);
                            canRicochet = false;
                            continue;
                        }

                        context.RemoveThing(index);
                        mem.State.ActIndex--;
                        switch (mem.GetElementType(targetElement))
                        {
                            case ElementType.Scroll:
                            case ElementType.Object:
                                context.SendLabel(-context.GetThingIndexAt(targetX, targetY), "SHOT", false);
                                break;
                        }
                        break;
                    }
                    else
                    {
                        if (mem.GetElement(targetElement).Points > 0)
                        {
                            mem.World.Score += mem.GetElement(targetElement).Points;
                            context.UpdateStatus();
                        }
                        context.Attack(index, targetX, targetY);
                        break;
                    }
                }
            }
        }

        static public void Clockwise(Context context, int index)
        {
            IMemory mem = context.Memory;
            IThing conveyor = mem.GetThing(index);

            context.UpdateBoardAt(conveyor.X, conveyor.Y);
            Convey(context, conveyor.X, conveyor.Y, 1);
        }

        static private void Convey(Context context, int x, int y, int direction)
        {
        }

        static public void Counter(Context context, int index)
        {
            IMemory mem = context.Memory;
            IThing conveyor = mem.GetThing(index);

            context.UpdateBoardAt(conveyor.X, conveyor.Y);
            Convey(context, conveyor.X, conveyor.Y, -1);
        }

        static public void DragonPup(Context context, int index)
        {
            IMemory mem = context.Memory;
            IThing dragonPup = mem.GetThing(index);

            context.UpdateBoardAt(dragonPup.X, dragonPup.Y);
        }

        static public void Duplicator(Context context, int index)
        {
        }

        static public void Head(Context context, int index)
        {
        }

        static public void Lion(Context context, int index)
        {
            IMemory mem = context.Memory;
            IThing lion = mem.GetThing(index);
            IVector vector = new VectorValue();

            if (lion.P1 >= context.Random(10))
                context.SetVectorSeek(lion.X, lion.Y, vector);
            else
                context.SetVectorRandom(vector);

            int tileID = context.GetIDAt(lion.X + vector.X, lion.Y + vector.Y);

            if (mem.GetElement(tileID).Floor)
                context.MoveThing(index, lion.X + vector.X, lion.Y + vector.Y);
            else if (mem.GetElementType(tileID) == ElementType.Player)
                context.Attack(index, lion.X + vector.X, lion.Y + vector.Y);
        }

        static public void Messenger(Context context, int index)
        {
            IMemory mem = context.Memory;
            IThing messenger = mem.GetThing(index);
            IState state = mem.State;

            if (messenger.X == 0)
            {
                context.DrawMessage((messenger.P2 % 7) + 9);
                messenger.P2--;
                if (messenger.P2 <= 0)
                {
                    context.RemoveThing(index);
                    state.ActIndex--;
                    context.UpdateBorder();
                    state.Message[0] = 0;
                }
            }
        }

        static public void Monitor(Context context, int index)
        {
            IState state = context.Memory.State;
            if (state.KeyPressed == 0x1B)
                state.BreakGameLoop = true;
        }

        static private void MoveRiver(Context context, int index)
        {
        }

        static public void Null(Context context, int index)
        {
        }

        static public void Object(Context context, int index)
        {
        }

        static public void Pairer(Context context, int index)
        {
            // original ZZT engine has empty subroutine
        }

        static public void Player(Context context, int index)
        {
        }

        static public void Pusher(Context context, int index)
        {
        }

        static public void Roton(Context context, int index)
        {
            IMemory mem = context.Memory;
            IThing roton = mem.GetThing(index);

            roton.P3--;
            if (roton.P3 < -roton.P2 * 10)
                roton.P3 = (roton.P2 * 10) + context.Random(10);

            context.SetVectorSeek(roton.X, roton.Y, roton.Vector);
            if (roton.P1 <= context.Random(10))
            {
                int sx = roton.StepX;
                roton.StepX = -context.PosNeg(roton.P2) * roton.StepY;
                roton.StepY = context.PosNeg(roton.P2) * sx;
            }

            int tileID = context.GetIDAt(roton.X + roton.StepX, roton.Y + roton.StepY);

            if (mem.GetElement(tileID).Floor)
                context.MoveThing(index, roton.X + roton.StepX, roton.Y + roton.StepY);
            else if (mem.GetElementType(tileID) == ElementType.Player)
                context.Attack(index, roton.X + roton.StepX, roton.Y + roton.StepY);
        }

        static public void Ruffian(Context context, int index)
        {
        }

        static public void Scroll(Context context, int index)
        {
            IMemory mem = context.Memory;
            IThing scroll = mem.GetThing(index);

            int color = context.GetColorAt(scroll.X, scroll.Y);
            color++;
            if (color > 0x0F)
                color = 0x09;
            context.SetColorAt(scroll.X, scroll.Y, color);
            context.UpdateBoardAt(scroll.X, scroll.Y);
        }

        static public void Segment(Context context, int index)
        {
            IMemory mem = context.Memory;
            IThing segment = mem.GetThing(index);

            if (segment.Leader < 0)
            {
                if (segment.Leader < -1)
                    mem.Board.SetTileID(segment.X, segment.Y, mem.GetElementIndexFromType(ElementType.Head));
                else
                    segment.Leader--;
            }
        }

        static public void Shark(Context context, int index)
        {
        }

        static public void Slime(Context context, int index)
        {
        }

        static public void Spider(Context context, int index)
        {
        }

        static private void SpiderMove(Context context, int index)
        {
        }

        static public void SpinningGun(Context context, int index)
        {
        }

        static public void Star(Context context, int index)
        {
        }

        static public void Stone(Context context, int index)
        {
            IMemory mem = context.Memory;
            IThing stone = mem.GetThing(index);

            context.SetColorAt(stone.X, stone.Y, context.Random(7) + 9 + (context.GetColorAt(stone.X, stone.Y) & 0x70));
            context.UpdateBoardAt(stone.X, stone.Y);
        }

        static public void Tiger(Context context, int index)
        {
            IMemory mem = context.Memory;
            IThing player = mem.GetThing(0);
            IThing tiger = mem.GetThing(index);
            ElementType firingElement;
            bool shot;

            firingElement = (tiger.P2 >= 0x80) ? ElementType.Star : ElementType.Bullet;
            if ((tiger.P2 & 0x7F) > (context.Random(10) * 3))
            {
                if (AbsDiff(tiger.X, player.X) > 2)
                    shot = false;
                else
                    shot = context.SpawnProjectile(mem.GetElementIndexFromType(firingElement), tiger.X, tiger.Y, 0, context.PosNeg(player.Y - tiger.Y), true);

                if (!shot && AbsDiff(tiger.Y, player.Y) > 2)
                    shot = context.SpawnProjectile(mem.GetElementIndexFromType(firingElement), tiger.X, tiger.Y, context.PosNeg(player.X - tiger.X), 0, true);
            }

            Lion(context, index);
        }

        static public void Transporter(Context context, int index)
        {
            IMemory mem = context.Memory;
            IThing transporter = mem.GetThing(index);

            context.UpdateBoardAt(transporter.X, transporter.Y);
        }
    }
}
